#include <inttypes.h>
#include <iostream>
#include <chrono>
#include <thread>

#include "sparta/sparta.hpp"
#include "sparta/simulation/TreeNode.hpp"
#include "sparta/utils/SpartaTester.hpp"
#include "sparta/log/Tap.hpp"
#include "sparta/log/MessageSource.hpp"

/*!
 * \file main.cpp
 * \brief Test for Logging in a simple tree
 */

TEST_INIT

// Test for expected observation status
#define EXPECT_NUM_OBSERVATION_POINTS(ms, num_pts_expected) {              \
        if(num_pts_expected > 0){                                       \
            EXPECT_TRUE(ms.observed());                                 \
        }else{                                                          \
            EXPECT_FALSE(ms.observed());                                \
        }                                                               \
                                                                        \
        EXPECT_EQUAL(ms.getNumObservationPoints(), num_pts_expected);   \
                                                                        \
        /* Print observation points */                                  \
        const std::vector<sparta::TreeNode*>& obs_pts = ms.getObservationPoints(); \
        std::cout << "Observation points on " << ms << ":" << std::endl; \
        for(sparta::TreeNode* n : obs_pts){                               \
            std::cout << "  @ " << *n << std::endl;                     \
        }                                                               \
    }

int main()
{
    // Build Tree

    sparta::RootTreeNode top("top");
    sparta::TreeNode a("a", "A node");
    sparta::TreeNode b("b", "B node");
    sparta::TreeNode c("c", "C node");
    sparta::TreeNode d("d", "D node");
    sparta::TreeNode e("e", "E node");
    sparta::TreeNode f("f", "F node");
    sparta::TreeNode g("g", "G node");


    /* Build Tree
     * -------------------------------------------------------------------------------------------------------------------------------
     *
     *                                                   (global virtual) ========= tap_everything  ""  everything.log.basic
     *                                                           .
     *                                                           .
     *                                                          top
     *                                                           |
     *                                                           |
     *                                                           |
     *                                                           a
     *                                                          /|\
     *                                                         / | \
     *                                                      __/  |  \__
     *                                                     /     |     \
     *                                                    /      |      \
     *                                                   /       |       \
     *                                                  b        d        e
     *                                                  |                 .
     *                                                  |                 .
     *                                                  c                 g
     *                                                                     ^
     *                                                                      \
     *                                                                       g
     *
     * -------------------------------------------------------------------------------------------------------------------------------
     */

    top.addChild(a);
    a.addChild(b);
    b.addChild(c);
    a.addChild(d);
    a.addChild(e);


    // Create valid message sources

    sparta::log::MessageSource c_src_mycategory(&c, "mycategory", "Messages generated by node c");
    sparta::log::MessageSource d_src_othercategory(&d, "other_category", "Messages generated by node d which will only be observed by a catch-all");

    EXPECT_FALSE(c_src_mycategory.observed());
    EXPECT_FALSE(d_src_othercategory.observed());

    // Tap on global pseudo-node capturing warnings. Must capture warnings from MessageSources created before and after this
    sparta::log::Tap tap_everything(sparta::TreeNode::getVirtualGlobalNode(), "", "everything.log.basic");

    EXPECT_TRUE(c_src_mycategory.observed());
    EXPECT_TRUE(d_src_othercategory.observed());

    // Create a Message Source on an unattached node

    EXPECT_FALSE(g.isAttached());
    sparta::log::MessageSource g_src_mycategory(&g, "mycategory", "Messages generated by node g");
    sparta::log::MessageSource g_src_warn(&g, sparta::log::categories::WARN, "Warning messages from g");

    EXPECT_TRUE(g_src_mycategory.observed()); // By tap_everything
    EXPECT_TRUE(g_src_warn.observed()); // By tap_everything
    g_src_mycategory << "This message SHOULD ACTUALLY BE OBSERVED"; // Nothing observing mycategory above g (not part of tree)

    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_warn, 1);

    g_src_warn << "This warning SHOULD be observed by warn.log.basic and global_warn.log.basic and that is it!"; // Node is not in tree

    EXPECT_TRUE(c_src_mycategory.observed());
    EXPECT_TRUE(d_src_othercategory.observed());
    EXPECT_TRUE(g_src_mycategory.observed());

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);

    c_src_mycategory << "Test Message 1" << " with some numbers like " << 1 << " and " << 2 << ". Neat";

    // Adding child (g) which already has a message source (g_src_mycategory) to tree.
    // Ensure that e_tap_mycategory gets messages from g_src_mycategory
    e.addChild(g);

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);

    top.enterConfiguring();
    sparta::log::MessageSource c_src_all(&c, "", "not_examined.log"); // Legal to create create during configuring

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);


    // Tap after finalization

    top.enterFinalized();
    EXPECT_THROW(sparta::log::MessageSource c_src_mycategory(&c, "", "desc")); // Cannot create during finalization

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);


    // Generate some test messages

    // These messages are observed by both a_tap_all and top_tap_all to "all.log.basic", which should contain 1 copy.
    c_src_mycategory << "Message from C in category 'mycategory'";
    c_src_mycategory << "Another message from C in category 'mycategory' with a new\nline char in the middle that should be converted to a \"\"";
    g_src_mycategory << "Message from G. Should be seen by e_tap_mycategory, a_tap_mycategory";
    d_src_othercategory << "Message from D. Should be seen by top_tap_all, a_tap_all, and a_tap_stder";

    EXPECT_NUM_OBSERVATION_POINTS(c_src_mycategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(d_src_othercategory, 1);
    EXPECT_NUM_OBSERVATION_POINTS(g_src_mycategory, 1);


    // It is important that this does not segfault because of the deletion of a_tap_removed
    c_src_mycategory << "Another message from C in category 'mycategory' after removing the tap to a_removed.log.basic";
    g_src_mycategory << "Message from G. Should be seen by e_tap_mycategory but NOT a_removed";

    g_src_warn << "Another warning after removing the temporary tap on a";

    // Check TreeNode statuses

    EXPECT_TRUE(c_src_mycategory.canGenerateNotification(typeid(sparta::log::Message), ""));
    EXPECT_TRUE(c_src_mycategory.canGenerateNotification(typeid(sparta::log::Message), "mycategory"));
    EXPECT_TRUE(c_src_mycategory.canGenerateNotification(typeid(sparta::log::Message), sparta::StringManager::getStringManager().internString("mycategory")));

    EXPECT_FALSE(top.canGenerateNotification(typeid(sparta::log::Message), "")); // Is not a message source
    EXPECT_FALSE(c_src_mycategory.canGenerateNotification(typeid(sparta::log::Message), "not_a_category")); // Category not used here
    EXPECT_FALSE(c_src_mycategory.canGenerateNotification(typeid(sparta::log::Message), sparta::StringManager::getStringManager().internString("not_a_category"))); // Category not used here

    EXPECT_TRUE(top.canSubtreeGenerateNotification(typeid(sparta::log::Message), ""));
    EXPECT_TRUE(top.canSubtreeGenerateNotification(typeid(sparta::log::Message), "mycategory"));
    EXPECT_TRUE(d.canSubtreeGenerateNotification(typeid(sparta::log::Message), "")); // d has a source

    EXPECT_FALSE(d.canSubtreeGenerateNotification(typeid(sparta::log::Message), "mycategory")); // D source is different category
    EXPECT_FALSE(top.canSubtreeGenerateNotification(typeid(sparta::log::Message), "not_a_category")); // Category not used here


    // Look at output files

    EXPECT_FILES_EQUAL("everything.log.basic.EXPECTED",     "everything.log.basic");


    // Print out the tree at different levels with different options

    std::cout << "The tree from the top: " << std::endl << top.renderSubtree(-1, true) << std::endl;

    std::cout << "\nLogging destination list\n";
    sparta::log::DestinationManager::dumpDestinations(std::cout);

    std::cout << "\nLogging destination file extensions\n";
    sparta::log::DestinationManager::dumpFileExtensions(std::cout);

    // Ensure that there are no duplicate destinations by counting
    EXPECT_EQUAL(sparta::log::DestinationManager::getNumDestinations(), 1); // everything.log.basic

    top.enterTeardown();

    // Done

    REPORT_ERROR;

    return ERROR_CODE;
}
